﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.ServiceModel;

namespace MSTestEx
{
    public class TestServiceClient<TInterface, TA> : IDisposable
        where TInterface : class
        where TA : TInterface
    {
        private static readonly Dictionary<Type, ServiceHost> ServiceHosts;
        private static readonly Dictionary<Type, string> ServiceAddresses;

        [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Needs to subscribe to the domain unload event.")]
        static TestServiceClient()
        {
            ServiceHosts = new Dictionary<Type, ServiceHost>();
            ServiceAddresses = new Dictionary<Type, string>();
            AppDomain.CurrentDomain.DomainUnload += CloseHosts;
        }

        ~TestServiceClient()
        {
            Dispose(false);
        }

        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "serviceHost is added to a member field dictionary.")]
        public TestServiceClient()
        {
            if (!ServiceHosts.ContainsKey(typeof(TA)))
            {
                ServiceAddresses[typeof(TA)] = "net.pipe://localhost/" + Guid.NewGuid();

                var baseAddress = new Uri(ServiceAddresses[typeof(TA)]);
                var serviceHost = new ServiceHost(typeof(TA), new[] { baseAddress });
                serviceHost.AddServiceEndpoint(typeof(TInterface), new NetNamedPipeBinding(), string.Empty);

                ServiceHosts[typeof(TA)] = serviceHost;

                serviceHost.Open();
            }

            Client = ChannelFactory<TInterface>.CreateChannel(new NetNamedPipeBinding(), new EndpointAddress(ServiceAddresses[typeof(TA)]));
        }

        public TInterface Client { get; private set; }

        public void Dispose()
        {
            Dispose(true);
        }

        private void Dispose(bool managed)
        {
            if (managed)
            {
                var channel = Client as IServiceChannel;

                if (channel != null && channel.State != CommunicationState.Faulted)
                {
                    channel.Close();

                    var disposable = Client as IDisposable;

                    if (disposable != null)
                    {
                        disposable.Dispose();
                        Client = null;
                    }
                }
            }
        }

        private static void CloseHosts(object sender, EventArgs e)
        {
            foreach (ServiceHost serviceHost in ServiceHosts.Values)
            {
                if (serviceHost.State == CommunicationState.Opened)
                {
                    serviceHost.Close();
                }
            }
        }
    }

}
